﻿using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using Moki.Core;
using Moki;

namespace Moki.Extensions
{
    internal static class ExpressionExtensions
    {
        internal static System.Reflection.MethodInfo GetMethod(this Expression<Action> callExpression)
        {
            var methodCall = callExpression.GetMethodCall();
            return methodCall.Method;
        }

        internal static System.Reflection.PropertyInfo GetProperty(this Expression<Func<object>> propertyExpression)
        {
            MemberExpression member;
            
            if (propertyExpression.Body is UnaryExpression)
                member = (MemberExpression)((UnaryExpression)propertyExpression.Body).Operand;
            else
                member = (MemberExpression)propertyExpression.Body;

            return (System.Reflection.PropertyInfo)member.Member;
        }

        internal static System.Reflection.MethodInfo GetPropertyMethod(this Expression<Func<object>> propertyExpression)
        {
            return GetProperty(propertyExpression).GetGetMethod();
        }

        internal static MethodCallExpression GetMethodCall(this Expression<Action> callExpression)
        {
            return (MethodCallExpression)callExpression.Body;
        }

        internal static object GetObjectByInvoke(this Expression<Action> callExpression)
        {
            // Transforms () => mock.Foo.Bar.Method() to () => mock.Foo.Bar  and invoke
            var methodCall = callExpression.GetMethodCall();
            LambdaExpression targetOnly = Expression.Lambda(methodCall.Object, null);
            Delegate compiled = targetOnly.Compile();
            object compiledObj = null;
            
            try
            {
                 compiledObj = compiled.DynamicInvoke(null);
            }
            catch (TargetInvocationException ex)
            {
                throw ex.InnerException;
            }

            return compiledObj;
        }

        internal static object GetObjectByInvoke(this Expression<Func<object>> propertyExpression)
        {
            MemberExpression propExpr;

            if (propertyExpression.Body is UnaryExpression)
                propExpr = (MemberExpression)((UnaryExpression)propertyExpression.Body).Operand;
            else
                propExpr = (MemberExpression)propertyExpression.Body;

            var memberExpr = (MemberExpression)propExpr.Expression;
            LambdaExpression targetOnly = Expression.Lambda(memberExpr, null);
            Delegate compiled = targetOnly.Compile();
            
            object compiledObj = null;

            try
            {
                compiledObj = compiled.DynamicInvoke(null);
            }
            catch (TargetInvocationException ex)
            {
                throw ex.InnerException;
            }

            return compiledObj;
        }

        internal static object[] GetMethodArgsArray(this Expression<Action> action)
        {
            var methodArgumentsCollection = action.GetMethodCall().Arguments;
            object[] methodArguments = methodArgumentsCollection.Select(c =>
                     Expression.Lambda(c)
                    .Compile()
                    .DynamicInvoke()).ToArray();

            return methodArguments;
        }

        internal static Arguments GetMethodArgs(this Expression<Action> action)
        {
            return new Arguments(GetMethodArgsArray(action));
        }

        internal static Arguments GetMethodArgsWithMatchers(this Expression<Action> action)
        {
            var methodArgumentsCollection = action.GetMethodCall().Arguments;
            object[] values = methodArgumentsCollection.Select(c =>
            {
                MatchersBuilder.InitNewPostition();
                return Expression.Lambda(c)
                    .Compile()
                    .DynamicInvoke();

            }).ToArray();

            Matcher[] matchers = MatchersBuilder.GetMatchersArray();
            MatchersBuilder.Reset();
            return new Arguments(values, matchers); 
        }
    }
}
